const defaultShaderType = [
    'VERTEX_SHADER',
    'FRAGMENT_SHADER',
];

export default class webglUtils {

    /**
     * 创建并编译一个着色器
     *
     * @param {!WebGLRenderingContext} gl WebGL上下文。
     * @param {string} shaderSource GLSL 格式的着色器代码
     * @param {number} shaderType 着色器类型, VERTEX_SHADER 或 FRAGMENT_SHADER。
     * @return {!WebGLShader} 着色器。
     */
    static compileShader(gl, shaderSource, shaderType) {
        // 创建着色器程序
        var shader = gl.createShader(shaderType);

        // 设置着色器的源码
        gl.shaderSource(shader, shaderSource);

        // 编译着色器
        gl.compileShader(shader);

        // 检测编译是否成功
        var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
        if (!success) {
            // 编译过程出错，获取错误信息。
            throw "could not compile shader:" + gl.getShaderInfoLog(shader);
        }

        return shader;
    }


    /**
    * 从 2 个着色器中创建一个程序
    *
    * @param {!WebGLRenderingContext} gl WebGL上下文。
    * @param {!WebGLShader} vertexShader 一个顶点着色器。
    * @param {!WebGLShader} fragmentShader 一个片段着色器。
    * @return {!WebGLProgram} 程序
    */
    static createProgram(gl, vertexShader, fragmentShader) {
        // 创建一个程序
        var program = gl.createProgram();

        // 附上着色器
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);

        // 链接到程序
        gl.linkProgram(program);

        // 检查链接是否成功
        var success = gl.getProgramParameter(program, gl.LINK_STATUS);
        if (!success) {
            // 链接过程出现问题
            throw ("program failed to link:" + gl.getProgramInfoLog(program));
        }

        return program;
    };

    /**
    * 用 script 标签的内容创建着色器
    *
    * @param {!WebGLRenderingContext} gl WebGL上下文。
    * @param {string} scriptId script标签的id。
    * @param {string} opt_shaderType. 要创建的着色器类型。如果没有定义，就使用script标签的type属性。
    * @return {!WebGLShader} 着色器。
    */
    static createShaderFromScript(gl, scriptId, opt_shaderType) {
        // 通过id找到script标签
        var shaderScript:any = document.getElementById(scriptId);
        if (!shaderScript) {
            throw ("*** Error: unknown script element" + scriptId);
        }

        // 提取标签内容。
        var shaderSource = shaderScript.text;

        // 如果没有传着色器类型，就使用标签的 ‘type’ 属性
        if (!opt_shaderType) {
            if (shaderScript.type == "x-shader/x-vertex") {
                opt_shaderType = gl.VERTEX_SHADER;
            } else if (shaderScript.type == "x-shader/x-fragment") {
                opt_shaderType = gl.FRAGMENT_SHADER;
            } else if (!opt_shaderType) {
                throw ("*** Error: shader type not set");
            }
        }

        return this.compileShader(gl, shaderSource, opt_shaderType);
    };


    /**
    * 通过两个 script 标签创建程序。
    * @param {!WebGLRenderingContext} gl WebGL上下文。
    * @param {string} vertexShaderId 顶点着色器的标签id。
    * @param {string} fragmentShaderId 片段着色器的标签id。
    * @return {!WebGLProgram} 程序。
    */
    static createProgramFromScripts(gl, vertexShaderId, fragmentShaderId) {
        var vertexShader = this.createShaderFromScript(gl, vertexShaderId, gl.VERTEX_SHADER);
        var fragmentShader = this.createShaderFromScript(gl, fragmentShaderId, gl.FRAGMENT_SHADER);
        return this.createProgram(gl, vertexShader, fragmentShader);
    }

    static createProgramFromGLSL(gl, vertexsShaderSource, fragmentShaderSource) {
        var vertexShader = this.compileShader(gl, vertexsShaderSource, gl.VERTEX_SHADER);
        var fragmentShader = this.compileShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
        return this.createProgram(gl, vertexShader, fragmentShader);
    }



    /**
    * 调整画布的大小以匹配其显示的大小。
    * @param {HTMLCanvasElement} canvas .
    * @param {number} [multiplier] 要乘以的数量。传入本机像素的 window.devicePixelRatio。
    * @return {boolean} 是否重置大小.
    */
    static resizeCanvasToDisplaySize(canvas, multiplier = window.devicePixelRatio) {
        multiplier = multiplier || 1;
        const width = canvas.clientWidth * multiplier | 0;
        const height = canvas.clientHeight * multiplier | 0;
        if (canvas.width !== width || canvas.height !== height) {
            canvas.width = width;
            canvas.height = height;
            return true;
        }
        return false;
    }


    /**
    * Creates a program, attaches shaders, binds attrib locations, links the
    * program and calls useProgram.
    * @param {WebGLShader[]} shaders The shaders to attach
    * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in
    * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
    * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console
    *        on error. If you want something else pass an callback. It's passed an error message.
    */
    static createPrograms(gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
        // const errFn = opt_errorCallback || error;
        const program = gl.createProgram();
        shaders.forEach(function (shader) {
            gl.attachShader(program, shader);
        });
        if (opt_attribs) {
            opt_attribs.forEach(function (attrib, ndx) {
                gl.bindAttribLocation(
                    program,
                    opt_locations ? opt_locations[ndx] : ndx,
                    attrib
                );
            });
        }
        gl.linkProgram(program);

        // Check the link status
        const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
        if (!linked) {
            // something went wrong with the link
            const lastError = gl.getProgramInfoLog(program);
            // errFn('Error in program linking:' + lastError);

            gl.deleteProgram(program);
            return null;
        }
        return program;
    }


    /**
    * 从两个源创建一个程序
    *
    * @param {WebGLRenderingContext} gl 要使用的WebGLRenderingContext。
    * @param {string[]} shaderSourcess 着色器的源阵列。第一个假设为顶点着色器，第二个假设为片段着色器。
    * @param {string[]} [opt_attribs] 属性名称的数组。如果未传入，位置将按索引分配
    * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
    * @param {module:webgl-utils.ErrorCallback} opt_errorCallback 回调错误。默认情况下，它只是在出现错误时向控制台打印一个错误。如果您想要其他内容，请传递回调。它传递了一条错误消息。
    */
    static createProgramFromSources(gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {

        const shaders = [];
        for (let ii = 0; ii < shaderSources.length; ++ii) {
            shaders.push(
                this.loadShader(gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback)
            );
        }
        return this.createPrograms(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
    }


    /**
     * Loads a shader.
     * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
     * @param {string} shaderSource The shader source.
     * @param {number} shaderType The type of shader.
     * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors.
     * @return {WebGLShader} The created shader.
     */
    static loadShader(gl, shaderSource, shaderType, opt_errorCallback) {
        // const errFn = opt_errorCallback || error;
        // Create the shader object
        const shader = gl.createShader(shaderType);

        // Load the shader source
        gl.shaderSource(shader, shaderSource);

        // Compile the shader
        gl.compileShader(shader);

        // Check the compile status
        const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
        if (!compiled) {
            // Something went wrong during compilation; get the error
            const lastError = gl.getShaderInfoLog(shader);
            // errFn('*** Error compiling shader \'' + shader + '\':' + lastError + `\n` + shaderSource.split('\n').map((l, i) => `${i + 1}: ${l}`).join('\n'));
            gl.deleteShader(shader);
            return null;
        }

        return shader;
    }
}